<?php
/**
 * Parsing filter request.
 */

class Model_GridFilter
{
	protected $start = 0;
	protected $limit = 20;
	protected $sort = "";
	protected $dir = "DESC";
	protected $filters = array();

	public function __construct($fields) {

		$start = $limit = $sort = $dir = $filters = "";
		foreach ($fields as $field=>$value)
		{
			if ($field == 'filters' && !empty($value)) $filters = $value;
				
			if ($field == 'start' && !empty($value)) $start = $value;
			if ($field == 'limit' && !empty($value)) $limit = $value;
			if ($field == 'sort' && !empty($value)) $sort = $value;
			if ($field == 'dir' && !empty($value)) $dir = $value;
		}

		// get start and limit value from request
		$f = new Zend_Filter_Int();

		if ( $start ) {
			$this->start = $f->filter($start);
		}
		if ( $limit ) {
			$this->limit = $f->filter($limit);
		}
		if ( $sort ) {
			$sort = $sort.trim();
			// prevent possible sql injection by field quote
			if ( preg_match('/`/', $sort) == 0 ) {
				$this->sort = $sort;
			}
		}
		if ( $dir ) {
			$dir = strtoupper($dir).trim();
			if ( $dir == "DESC" || $dir == "ASC" ) {
				$this->dir = $dir;
			}
		};
		if ( $filters && is_array($filters) ) {
			$this->filters = $filters;
		}
	}

	public function getStart() {
		return $this->start;
	}

	public function getLimit() {
		return $this->limit;
	}

	/**
	 * Return the order query determine by sort and dir parameter.
	 * @param acceptFields, an array indicate which field available for order.
	 * The key is available field, and the value is possible prefix indicates
	 * table alias this field belongs to.
	 * @param default return this string is no matched.
	 */
	public function getOrderQuery($acceptFields, $default) {
		if ( array_key_exists($this->sort, $acceptFields) ) {
			$field = '`'.$this->sort.'`';
			$prefix = $acceptFields[$this->sort];
			if ( strlen($prefix) ) {
				$field = $prefix.'.'.$field;
			}
			return ' ORDER BY '.$field.' '.$this->dir;
		} else {
			return $default;
		}
	}

	/**
	 * Similar with get Order query, but return a string suitable for
	 * Zend_Db_Select order clause.
	 */
	public function getOrder($acceptFields, $default) {
		if ( array_key_exists($this->sort, $acceptFields) ) {
			return $this->sort.' '.$this->dir;
		} else {
			return $default;
		}
	}

	/**
	 * return query array good for Zend_Db_Select's where function.
	 * The item can array or string. for array, it should be 2 string,
	 * first is query template, second is value.
	 * The acceptFields is an array of field=>prefix pair.
	 */
	public function getFilters($acceptFields) {
		$result = array();
		foreach( $this->filters as $index => $filter ) {
			$fieldBase = $filter['field'];
			if ( array_key_exists($fieldBase, $acceptFields) ) {
				$prefix = $acceptFields[$fieldBase];
				$field = '`'.$fieldBase.'`';
				if ( strlen($prefix) ) {
					$field = $prefix.'.'.$field;
				}
				$q = $this->makeFilterQuery($field, $filter['data']);
				if ( $q ) {
					$result[] = $q;
				}
			}
		}
		return $result;
	}

	private function makeFilterQuery($field, $data) {
		switch ($data['type']) {
			case 'string':
				return $this->makeStringFilterQuery($field, $data);
			case 'list':
				return $this->makeListFilterQuery($field, $data);
			case 'boolean':
				return $this->makeBooleanFilterQuery($field, $data);
			case 'numeric':
				return $this->makeNumericFilterQuery($field, $data);
			case 'date':
				return $this->makeDateFilterQuery($field, $data);
			default:
				error_log('Invalid filter data type '.$data['type'], 0);
				return null;
		}
	}

	private function makeStringFilterQuery($field, $data) {
		return array($field.'LIKE ?', '%'.$data['value'].'%');
	}

	private function makeListFilterQuery($field, $data) {
		// quote the comma seperated list
		$items = explode(',', $data['value']);
		$escaped = array();
		for ($i=0; $i<count($items); $i++ ) {
			$item = trim($items[$i]);
			if ( strlen($item) > 0 ) {
				$escaped[] = "'".$item."'";
			}
		}
		$value = implode(',', $escaped);
		if ( count($escaped) > 1 ) {
			return $field.' IN ('.$value.')';
		} else {
			return $field.'='.$value;
		}
	}

	private function makeBooleanFilterQuery($field, $data) {
		return array($field.'=?', $data['value']);
	}

	private function getCmp($symbol) {
		$cmp = '=';
		switch ( $symbol ) {
			case 'eq':
				$cmp = '=';
				break;
			case 'gt':
				$cmp = '>';
				break;
			case 'lt':
				$cmp = '<';
				break;
			default:
				error_log('invalid filter compare symbol '.$symbol, 0);
		}
		return $cmp;
	}

	private function makeNumericFilterQuery($field, $data) {
		$cmp = $this->getCmp($data['comparison']);
		return array($field.$cmp.'?', $data['value']);
	}

	private function makeDateFilterQuery($field, $data) {
		$cmp = $this->getCmp($data['comparison']);
		$dateStr = date('y-m-d', strtotime($data['value']));
		return array($field.$cmp.'?', $dateStr);
	}

	public function addFilter($select, $sortable) {
		foreach ($this->getFilters($sortable) as $filter) {
			if ( is_array($filter) ) {
				$select->where($filter[0], $filter[1]);
			} else {
				$select->where($filter);
			}
		}
		return $select;
	}
}